package com.game.commons.scripts.java.impl;

import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;

import com.game.commons.scripts.java.classlistener.ClassListener;
import com.game.commons.scripts.java.classlistener.DefaultClassListener;
import com.game.commons.scripts.java.CompilationResult;
import com.game.commons.scripts.java.ScriptCompiler;
import com.game.commons.scripts.java.ScriptContext;


/**
 * {@link com.game.commons.scripts.java.ScriptContext}的实现类
 * @author 杜祥
 * @create 2013-6-13
 */
public class ScriptContextImpl implements ScriptContext 
{
	private final static Logger log = Logger.getLogger(ScriptContextImpl.class);

	/**
	 * 脚本加载卸载类
	 */
	private final ScriptContext parentScriptContext;
	
	/**
	 * 类加载器加载的库
	 */
	private Iterable<File> libraries;
	
	/**
	 * 脚本加载卸载类跟目录，它和它的子目录都将被加载
	 */
	private final File root;
	
	
	/**
	 * 编译结果
	 */
	private CompilationResult compilationResult;
	
	
	/**
	 * 子脚本加载卸载类列表
	 */
	private Set<ScriptContext> childScriptContext;
	
	/**
	 * 类监听
	 */
	private ClassListener classListener;
	
	/**
	 * 编译器名称
	 */
	private String	compilerClassName;
	
	
	/**
	 * 创建一个新的ScriptContext实例，并定义根目录
	 * @param root
	 */
	public ScriptContextImpl(File root)
	{
		this(root, null);
	}
	
	
	/**
	 * 创建一个ScriptContext实例，并定义根目录和ScriptContext
	 * @param root
	 * 					跟目录
	 * @param parent
	 * 					ScriptContext
	 * @throws NullPointerException
	 * 					根目录为null的时候抛出
	 * @throws IllegalArgumentException
	 * 					当根目录不存在或则该对象不是目录抛出
	 */
	public ScriptContextImpl(File root, ScriptContext parent)
	{
		if(root == null)
		{
			throw new NullPointerException("必须指定跟目录！");
		}

		if(!root.exists() || !root.isDirectory())
		{
			throw new IllegalArgumentException("根目录不存在或则根目录不是一个目录");
		}

		this.root = root;
		this.parentScriptContext = parent;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public synchronized void init()
	{
		if(compilationResult != null)
		{
			log.error(new Exception("已经初始化过！"));
			return ;
		}
		ScriptCompiler scriptCompiler = instantiateCompiler();
		
		Collection<File> files = FileUtils.listFiles(root, scriptCompiler.getSupportedFileTypes(), Boolean.TRUE);
		
		if(parentScriptContext != null)
		{
			scriptCompiler.setParentClassLoader(parentScriptContext.getCompilationResult().getClassLoader());
		}
		
		scriptCompiler.setLibraires(libraries);
		compilationResult = scriptCompiler.compile(files);
		
		getClassListener().postLoad(compilationResult.getCompiledClassed());
		
		if(childScriptContext != null)
		{
			for(ScriptContext context : childScriptContext)
			{
				context.init();
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public synchronized void shutdown() 
	{
		if(compilationResult == null)
		{
			log.error("脚本加载卸载没有初始化！", new Exception());
			return;
		}
		
		if(childScriptContext != null)
		{
			for(ScriptContext context : childScriptContext)
			{
				context.shutdown();
			}
		}
		
		getClassListener().preUnload(compilationResult.getCompiledClassed());
		
		compilationResult = null;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void reload() 
	{
		shutdown();
		init();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public File getRoot()
	{
		return root;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public CompilationResult getCompilationResult()
	{
		return compilationResult;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public synchronized boolean isInitialized() 
	{
		return compilationResult != null;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setLibraries(Iterable<File> files) 
	{
		this.libraries = files;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Iterable<File> getLibraries()
	{
		return this.libraries;
	}
	
	
	/**
	 * 初始化脚本编译器
	 * @return			
	 * 					返回脚本编译器
	 * @throws RuntimeException
	 * 					无法创建编译器实例时抛出此异常
	 */
	protected ScriptCompiler instantiateCompiler() throws RuntimeException
	{
		ClassLoader cl = getClass().getClassLoader();
		
		if(getParentScriptContext() != null)
		{
			cl = getParentScriptContext().getCompilationResult().getClassLoader();
		}
		
		ScriptCompiler sc;
		try 
		{
			sc = (ScriptCompiler)Class.forName(getCompilerClassName(), true, cl).newInstance();
		}
		catch (Exception e)
		{
			RuntimeException runtime = new RuntimeException("无法创建编译器实例！", e);
			log.error(runtime);
			throw runtime;
		}
		
		return sc;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public ScriptContext getParentScriptContext() 
	{
		return parentScriptContext;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Collection<ScriptContext> getChildScriptContext()
	{
		return childScriptContext;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setClassListener(ClassListener listener) 
	{
		this.classListener = listener;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public ClassListener getClassListener() 
	{
		if(classListener == null)
		{
			if(getParentScriptContext() == null)
			{
				setClassListener(new DefaultClassListener());
				return classListener;
			}
			else
			{
				return getParentScriptContext().getClassListener();
			}
		}
		else
		{
			return classListener;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setCompilerClassName(String className)
	{
		this.compilerClassName = className;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String getCompilerClassName() 
	{
		return this.compilerClassName;
	}

	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public int hashCode() 
	{
		int result = parentScriptContext != null ? parentScriptContext.hashCode() : 0;
		result = result * 31 + root.hashCode();
		return result;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean equals(Object obj) 
	{
		if(!(obj instanceof ScriptContextImpl))
		{
			return false;
		}
		ScriptContextImpl another = (ScriptContextImpl)obj;
		if(parentScriptContext == null)
		{
			return another.getRoot().equals(root);
		}
		else
		{
			return another.getRoot().equals(root) && parentScriptContext.equals(another.parentScriptContext);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void finalize() throws Throwable 
	{
		if(compilationResult != null)
		{
			log.error("强制关闭脚本加载卸载！");
			shutdown();
		}
		super.finalize();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addChildScriptContext(ScriptContext context) 
	{
		synchronized (this)
		{
			if(childScriptContext == null)
			{
				childScriptContext = new HashSet<ScriptContext>();
			}
			
			if(childScriptContext.contains(context))
			{
				log.error("已经存在, root: " + root.getAbsolutePath() + ", child: "
						+ context.getRoot().getAbsolutePath());
				return;
			}
			
			if(isInitialized())
			{
				context.init();
			}
		}
		childScriptContext.add(context);
	}
	
	

}
